/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief Motti Mission decoder and master level container. 
 * \author Antti Maula <antti.maula@tkk.fi>
 */
#include "MottiMission.hpp"
#include "MottiMissionTypes.hpp"
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>
#include <assert.h>
#include <iostream>
#include <fstream>

//*****************************************************************************
using namespace Motti;
//*****************************************************************************

CMission::CMission()
  : iXMLData(),
    iMission(),
    iIsValidMission(false)
{
}
//*****************************************************************************

CMission::~CMission()
{
}
//*****************************************************************************

bool CMission::DecodeMottiMissionXMLFile(const std::string &aXMLDataFile, bool aSave)
{
  using namespace std;
  bool result = false;
  std::string xmldata;
  ifstream::pos_type size;
  char * memblock;
  
  ifstream file (aXMLDataFile.c_str(), ios::in|ios::binary|ios::ate);
  if (file.is_open()) {
    size = file.tellg();
    memblock = new char [1+(int)(size)];
    file.seekg (0, ios::beg);
    file.read (memblock, size);
    file.close();
    memblock[size] = '\0';
    xmldata = memblock;
    delete[] memblock;

    result = DecodeMottiMissionXML(xmldata, aSave);
  }


  return result;
}
//*****************************************************************************

bool CMission::EncodeMottiMission(const SMission &aMission, bool aSave)
{
  bool result = false;
  
  // Unconditional
  if (aSave)  iIsValidMission = false;

  // Check validity.
  result = aMission.Validate();

  // Convert
  if (result && aSave) {
    // Do.
    iIsValidMission = result;
    iMission = aMission;
    iXMLData = aMission.ToXMLString();
  }

  return result;
}
//*****************************************************************************

bool CMission::DecodeMottiMissionXML(const std::string &aXMLData, bool aSave)
{
  xmlDocPtr docp;
  xmlNodePtr nodep;
  bool result = false;
  SMission mission_decoded;

  // Unconditional false for now.
  iIsValidMission = false;

  docp = xmlRecoverDoc((xmlChar *) aXMLData.c_str());
  if (docp == NULL) {
    dPrint(ODWARN,"Failed to decode XML doc. Fatal error. Decoding aborted.");
    return false;
  }
  
  // Get root element
  nodep = xmlDocGetRootElement(docp);
  assert(nodep != NULL);

  // Check that we have a Mission
  if( xmlStrcmp(nodep->name, (xmlChar *) "mission")) {
    dPrint(ODWARN, "This is not a MottiMission XML document (Invalid root node: '%s')",
           nodep->name);
    
  } else {
    // Enter Mission tag decoder.
    result = DecodeTagMission(mission_decoded, docp, nodep);
    
  }
  

  // Free doc.
  if (docp) xmlFreeDoc(docp);


  // Last but not least, if the decoding was succesfull, store the
  // given input XML-data.
  if (result && aSave) {
    iXMLData = aXMLData;
    iMission = mission_decoded;
    iIsValidMission = true;
  }

  return result;
}
//*****************************************************************************

const SMission &CMission::GetMission(void) const
{
  return iMission;
}
//*****************************************************************************

const std::string &CMission::GetMissionXML(void) const
{
  return iXMLData;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//** Private interface ********************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

bool CMission::DecodeTagMission(SMission &aMission, xmlDocPtr docp, xmlNodePtr nodep)
{
  bool result = false;

  if (ExtractNodeHeader(aMission.nodeheader, docp, nodep)) {
    // Got Id, Rule and Trigger for mission.
#warning Validate Id, Rule and Trigger for mission use.
    
    // Enter children.
    result = true;
    nodep = nodep->children;
    
    // Now iterate through all elements at 'Mission' level.
    while(nodep && result) {
      if (nodep->type == XML_ELEMENT_NODE) {
        if ( !xmlStrcmp(nodep->name, (xmlChar *) "header") ) {
          result = DecodeTagMissionHeader(aMission.header, docp, nodep);
          
        } else if ( !xmlStrcmp(nodep->name, (xmlChar *) "pallets") ) {
          result = DecodeTagPallets(aMission.pallets, docp, nodep);
          
        } else {
          dPrint(ODERROR,"Unknown tag '%s' in 'Mission' level", 
                 nodep->name);

        }
      }
      
      // Traverse to next node in level.
      nodep = nodep->next;
    }

    // When all elements complete; re-assess the result by checking resulting
    // data.
#warning Check that elements are OK. (Parsing is succesfull)
    result = result;

  } else {
    dPrint(ODERROR,"Failed to parse header for Mission node");

  }
    
  return result;
}
//*****************************************************************************

bool CMission::DecodeTagMissionHeader(SMissionHeader &aHeader, xmlDocPtr docp, xmlNodePtr nodep)
{
  dPrint(ODDEBUG(1),"Decoding: Mission/Header");  
  bool result = true;
  SMissionHeader &mh = aHeader;
  bool 
    gotName=false,
    gotDescription=false,
    gotTime=false,
    gotPlatform=false,
    gotMissionLocation=false,
    gotTruckId=false;


  
  // Enter children.
  nodep = nodep->children;
  
  // Now iterate through all elements at 'Mission Header' level.
  while(nodep && result) {
    if (nodep->type == XML_ELEMENT_NODE) {
      xmlChar *content = xmlNodeListGetString(docp, nodep->xmlChildrenNode, 0);
      if (content) {
        if ( !xmlStrcmp(nodep->name, (xmlChar *) "name") ) {
          if (!gotName) {
            mh.name = reinterpret_cast<const char *>(content);
            gotName = true;
          } else result = false;
          
        } else if ( !xmlStrcmp(nodep->name, (xmlChar *) "description") ) {
          if (!gotDescription) {
            mh.description = reinterpret_cast<const char *>(content);
            gotDescription = true;
          } else result = false;

        } else if ( !xmlStrcmp(nodep->name, (xmlChar *) "time") ) {
          dPrint(ODDEBUG(1),"Ignoring 'time' tag, use only ctime.");

        } else if ( !xmlStrcmp(nodep->name, (xmlChar *) "ctime") ) {
          if (!gotTime) {
            mh.departure_time = gim::time( (long int)atol( reinterpret_cast<const char *>(content) ), (long int)0 );
            gotTime = true;
          } else result = false;

        } else if ( !xmlStrcmp(nodep->name, (xmlChar *) "platform") ) {
          if (!gotPlatform) {
            xmlNodePtr platformNode = nodep->children;
            while(platformNode && result) {
              if (platformNode->type == XML_ELEMENT_NODE) {
                if ( !xmlStrcmp(platformNode->name, (xmlChar *) "MissionLocation") ) {
                  if (!gotMissionLocation) {
                    result = ExtractMissionLocation(mh.MissionLocation, docp, platformNode->children);
                    gotMissionLocation = true;
                  } else result = false;
                  
                } else {
                  dPrint(ODERROR,"Unknown tag '%s' in 'Mission/Header/Platform' level", 
                         platformNode->name);
                  
                }
              }
              platformNode = platformNode->next;
            }
            gotPlatform = true;
          } else result = false;
          
        } else if ( !xmlStrcmp(nodep->name, (xmlChar *) "truckid") ) {
          if (!gotTruckId) {
            mh.truck_id = atol( reinterpret_cast<const char *>(content) );
            gotTruckId = true;
          } else result = false;

        } else {
          dPrint(ODERROR,"Unknown tag '%s' in 'Mission/Header' level", 
                 nodep->name);
          
        }

        xmlFree(content);
      }
    }
    
      // Traverse to next node in level.
    nodep = nodep->next;
  }


  // Now, check that all elements are present and decoding itself was fine.
  result = result && gotName && gotDescription && gotTime && 
    gotPlatform && gotMissionLocation && gotTruckId;


  return result;
}
//*****************************************************************************

bool CMission::DecodeTagPallets(SPallets &aPallets, xmlDocPtr docp, xmlNodePtr nodep)
{
  bool result = true;
  dPrint(ODDEBUG(1),"Decoding: Mission/Pallets");  

  // Enter children.
  nodep = nodep->children;
  
  // Now iterate through all elements at 'Mission/Pallets' level.
  while(nodep && result) {
    if (nodep->type == XML_ELEMENT_NODE) {
      if ( !xmlStrcmp(nodep->name, (xmlChar *) "pallet") ) {
        
        SPallet newpallet;
        if ( (result = DecodeTagPallet(newpallet, docp, nodep)) ) {
          // Insert the pallet.
          aPallets.AddPallet(newpallet);
        }

      }  else {
        dPrint(ODERROR,"Unknown tag '%s' in 'Mission/Pallets' level", 
               nodep->name);
        
      }
    }
    nodep = nodep->next;
  }

  return result;
}
//*****************************************************************************

bool CMission::DecodeTagPallet(SPallet &aPallet, xmlDocPtr docp, xmlNodePtr nodep)
{
  bool result = false;
  dPrint(ODDEBUG(1),"Decoding: Mission/Pallets/Pallet");

  // Stats and error checking
  unsigned int get_empty_count = 0;
  unsigned int fill_count = 0;
  unsigned int transport_count = 0;

  if ( (result = ExtractNodeHeader(aPallet.nodeheader, docp, nodep)) ) {
    
    // Enter children.
    nodep = nodep->children;
    
    // Now iterate through all elements at 'Mission/Pallets/Pallet' level.
    while(nodep && result) {
      if (nodep->type == XML_ELEMENT_NODE) {
        if ( !xmlStrcmp(nodep->name, (xmlChar *) "get_empty") ) {
          result = DecodeTagGetEmpty(aPallet.get_empty, docp, nodep);
          get_empty_count += result;
          
        } else if ( !xmlStrcmp(nodep->name, (xmlChar *) "fill") ) {
          result = DecodeTagFill(aPallet.fill, docp, nodep);
          fill_count += result;
          
        } else if ( !xmlStrcmp(nodep->name, (xmlChar *) "transport") ) {
          result = DecodeTagTransport(aPallet.transport, docp, nodep);
          transport_count += result;
          
        } else {
          dPrint(ODERROR,"Unknown tag '%s' in 'Mission/Pallets/Pallet' level", 
                 nodep->name);
          
        }
      }
      nodep = nodep->next;
    }
  }

  // Validate: Get empty must be 1, transport must be 1 and fill count
  // can be 0 or 1
  result &= get_empty_count == 1 && transport_count == 1 && fill_count < 2;
  
  return result;
}
//*****************************************************************************

bool CMission::DecodeTagGetEmpty(SGetEmpty &aGetEmpty, xmlDocPtr docp, xmlNodePtr nodep)
{
  dPrint(ODDEBUG(1),"Decoding: Mission/Pallets/Pallet/GetEmpty");
  bool result = false;
  
  // MissionLocation elements fetched.
  unsigned int MissionLocation_count = 0;

  if (ExtractNodeHeader(aGetEmpty.nodeheader, docp, nodep)) {
    // Enter children.
    nodep = nodep->children;
    
    // Now iterate through all elements at 'Mission/Pallets/Pallet/GetEmpty' level.
    result = true;
    while(nodep && result) {
      if (nodep->type == XML_ELEMENT_NODE) {
        if ( !xmlStrcmp(nodep->name, (xmlChar *) "MissionLocation") ) {
          result = ExtractMissionLocation(aGetEmpty.MissionLocation, docp, nodep->children);
          MissionLocation_count += result;
          
        } else {
          dPrint(ODERROR,"Unknown tag '%s' in 'Mission/Pallets/Pallet/GetEmpty' level", 
                 nodep->name);

        }
      }
      
      // Traverse to next node in level.
      nodep = nodep->next;
    }
  }
  
  // Verify that exactly one MissionLocation was found.
  result &= MissionLocation_count == 1;

  return result;
}
//*****************************************************************************

bool CMission::DecodeTagFill(SFill &aFill, xmlDocPtr docp, xmlNodePtr nodep)
{
  dPrint(ODDEBUG(1),"Decoding: Mission/Pallets/Pallet/Fill");
  bool result = false;
  
  if (ExtractNodeHeader(aFill.nodeheader, docp, nodep)) {

    // Enter children.
    nodep = nodep->children;
    
    // Now iterate through all elements at 'Mission/Pallets/Pallet/GetEmpty' level.
    result = true;
    while(nodep && result) {
      if (nodep->type == XML_ELEMENT_NODE) {
        if ( !xmlStrcmp(nodep->name, (xmlChar *) "pick") ) {
          SPick newpick;
          if ( (result = DecodeTagPick(newpick, docp, nodep)) ) {
            aFill.AddPick(newpick);
          }

        } else {
          dPrint(ODERROR,"Unknown tag '%s' in 'Mission/Pallets/Pallet/Fill' level", 
                 nodep->name);

        }
      }
      
      // Traverse to next node in level.
      nodep = nodep->next;
    }
  }



  return result;
}
//*****************************************************************************

bool CMission::DecodeTagTransport(STransport &aTransport, xmlDocPtr docp, xmlNodePtr nodep)
{
  dPrint(ODDEBUG(1),"Decoding: Mission/Pallets/Pallet/Transport");
  bool result = false;


  unsigned int MissionLocation_count = 0;
  
  if (ExtractNodeHeader(aTransport.nodeheader, docp, nodep)) {

    // Enter children.
    nodep = nodep->children;
    
    // Now iterate through all elements at 'Mission/Pallets/Pallet/GetEmpty' level.
    result = true;
    while(nodep && result) {
      if (nodep->type == XML_ELEMENT_NODE) {
        if ( !xmlStrcmp(nodep->name, (xmlChar *) "MissionLocation") ) {
          SNodeHeader nh;
          result = ExtractNodeHeader(nh, docp, nodep);
          
          SMissionLocation l;
          if (result && (result = ExtractMissionLocation(l, docp, nodep->children)) ) {
            l.nodeheader = nh;
            aTransport.AddMissionLocation(l);
            MissionLocation_count += 1;
          }
          
        } else {
          dPrint(ODERROR,"Unknown tag '%s' in 'Mission/Pallets/Pallet/Transport' level", 
                 nodep->name);

        }
      }
      
      // Traverse to next node in level.
      nodep = nodep->next;
    }
  }

  // Verify
  result &= MissionLocation_count >= 1;


  return result;
}
//*****************************************************************************

bool CMission::DecodeTagPick(SPick &aPick, xmlDocPtr docp, xmlNodePtr nodep)
{
  dPrint(ODDEBUG(1),"Decoding: Mission/Pallets/Pallet/Fill/Pick");
  bool result = false;

  unsigned int item_name_count = 0;
  unsigned int item_id_count = 0;
  unsigned int quantity_count = 0;
  unsigned int MissionLocation_count = 0;
  
  if (ExtractNodeHeader(aPick.nodeheader, docp, nodep)) {
    // Enter children.
    nodep = nodep->children;
    
    // Now iterate through all elements at 'Mission/Pallets/Pallet/GetEmpty' level.
    result = true;
    while(nodep && result) {
      if (nodep->type == XML_ELEMENT_NODE) {
        xmlChar *content = xmlNodeListGetString(docp, nodep->xmlChildrenNode, 0);
        if (content) {  
          if ( !xmlStrcmp(nodep->name, (xmlChar *) "item_name") ) {
            aPick.item_name = reinterpret_cast<const char *>(content);
            item_name_count += 1;

          } else if ( !xmlStrcmp(nodep->name, (xmlChar *) "item_id") ) {
            aPick.item_id = atol( reinterpret_cast<const char *>(content) );
            item_id_count += 1;

          } else if ( !xmlStrcmp(nodep->name, (xmlChar *) "quantity") ) {
            aPick.quantity = reinterpret_cast<const char *>(content);
            quantity_count += 1;
            
          } else if ( !xmlStrcmp(nodep->name, (xmlChar *) "MissionLocation") ) {
            SNodeHeader nh;
            result = ExtractNodeHeader(nh, docp, nodep);
            
            SMissionLocation l;
            if (result && (result = ExtractMissionLocation(l, docp, nodep->children)) ) {
              l.nodeheader = nh;
              aPick.AddMissionLocation(l);
              MissionLocation_count += 1;
            }
            
          } else {
            dPrint(ODERROR,"Unknown tag '%s' in 'Mission/Pallets/Pallet/Fill' level", 
                   nodep->name);
            
          }
          xmlFree(content);
        }
      }
      
      // Traverse to next node in level.
      nodep = nodep->next;
    }
  }


  // Validity check
  result &= 
    item_name_count == 1 && 
    item_id_count == 1 && 
    quantity_count == 1 && 
    MissionLocation_count >= 1;


  return result;
}
//*****************************************************************************

bool CMission::ExtractMissionLocation(SMissionLocation &aMissionLocation,
                               xmlDocPtr docp, xmlNodePtr nodep)
{
  bool gotMissionLocationX=false, gotMissionLocationY=false, gotSlot=false;
  bool result = true;

  while(nodep && result) {
    if (nodep->type == XML_ELEMENT_NODE) {
      xmlChar *content = xmlNodeListGetString(docp, nodep->xmlChildrenNode, 0);
      if (content) {
        if ( !xmlStrcmp(nodep->name, (xmlChar *) "x") ) {
          if (!gotMissionLocationX) {
            aMissionLocation.x = atof( reinterpret_cast<const char *>(content) );
            gotMissionLocationX = true;
          } else result = false;
          
        } else if ( !xmlStrcmp(nodep->name, (xmlChar *) "y") ) {
          if (!gotMissionLocationY) {
            aMissionLocation.y = atof( reinterpret_cast<const char *>(content) );
            gotMissionLocationY = true;
          } else result = false;
          
       } else if ( !xmlStrcmp(nodep->name, (xmlChar *) "slot") ) {
          if (!gotSlot) {
            aMissionLocation.slot = atoi( reinterpret_cast<const char *>(content) );
            gotSlot = true;
          } else result = false;
          
        } else {
          dPrint(ODERROR,"Unknown tag '%s' in 'MissionLocation' sub-level", 
                 nodep->name);
        }
        
        xmlFree(content);
      }
    }
    nodep = nodep->next;
  }
  
  //  return result && gotMissionLocationX && gotMissionLocationY && gotSlot;
  return result && gotMissionLocationX && gotMissionLocationY;
}
//*****************************************************************************
 
bool CMission::ExtractNodeHeader(SNodeHeader &aNodeHeader,
                                 xmlDocPtr docp, xmlNodePtr nodep)
{
  return ExtractNodeHeader(aNodeHeader.id,
                           aNodeHeader.rule,
                           aNodeHeader.trigger,
                           docp,
                           nodep);
}
//*****************************************************************************
 

bool CMission::ExtractNodeHeader(std::string &aId, 
                                 std::string &aRule, 
                                 std::string &aTrigger, 
                                 xmlDocPtr docp, xmlNodePtr nodep)
{
  bool result = false;
  
  // 
  xmlChar *idStr = xmlGetProp(nodep, (xmlChar *) "ID");
  xmlChar *ruleStr = xmlGetProp(nodep, (xmlChar *) "rule");
  xmlChar *triggerStr = xmlGetProp(nodep, (xmlChar *) "trigger");

  if (idStr && ruleStr && triggerStr) {
    aId = reinterpret_cast<const char *>(idStr);
    aRule = reinterpret_cast<const char *>(ruleStr);
    aTrigger = reinterpret_cast<const char *>(triggerStr);
#warning Add sanity check here for ID, Rule and Trigger nodes.
    result = true;
    
  }


  if (idStr) xmlFree(idStr);
  if (ruleStr) xmlFree(ruleStr);
  if (triggerStr) xmlFree(triggerStr);



  return result;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
